package com.ymttest.database.sharding.config;

import io.shardingjdbc.core.api.ShardingDataSourceFactory;
import io.shardingjdbc.core.api.config.ShardingRuleConfiguration;
import io.shardingjdbc.core.api.config.TableRuleConfiguration;
import io.shardingjdbc.core.api.config.strategy.ComplexShardingStrategyConfiguration;
import io.shardingjdbc.core.jdbc.core.datasource.ShardingDataSource;

import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;

import com.alibaba.druid.pool.DruidDataSource;
import com.ymttest.database.sharding.ComplexKeysDatabaseAlgorithm;
import com.ymttest.database.sharding.ComplexKeysTableAlgorithm;

public class DbShardingSourceConfig {

	private static final String DB_NAME = "orderdb";

	private static DbShardingSourceConfig shardingSourceConfig = null;

	private ConnectionConfig connectionConfig = ConnectionConfig.getInstance();

	private DbShardingSourceConfig() {

	}

	public DataSource shardingDataSource() {
		
		int dbCount = connectionConfig.getShardingDbCount();
		
		//配置数据库实例个数
		try {
			Map<String, DataSource> dataSourceMap = new HashMap<>();
			for (int n = 1; n <= dbCount; n++) {
				
				DruidDataSource source = DataSourceUtils.initDataSource();
				
				source.setDriverClassName(com.mysql.jdbc.Driver.class.getName());

				if (!connectionConfig.getShardingDbUrl().contains("dbserver%s")) {
					//开发环境，数据库数据库没有域名化。各库共用一个固定IP
					source.setUrl(String.format(
							connectionConfig.getShardingDbUrl(),
							connectionConfig.getShardingDbPort() + n, n));
				} else {
					source.setUrl(String.format(
							connectionConfig.getShardingDbUrl(), n,
							connectionConfig.getShardingDbPort() + n, n));
				}
				
				source.setUsername(connectionConfig.getShardingDbUser());
				source.setPassword(connectionConfig.getShardingDbPassword());
				
				dataSourceMap.put(DB_NAME + "_" + n, source);
			}

			TableRuleConfiguration rule_Order = new TableRuleConfiguration();
			rule_Order.setLogicTable("order");
			rule_Order.setActualDataNodes(String.format(
					"%s_${1..%s}.order_${0..99}", DB_NAME, dbCount));

			TableRuleConfiguration rule_MainOrder = new TableRuleConfiguration();
			rule_MainOrder.setLogicTable("main_order");
			rule_MainOrder.setActualDataNodes(String.format(
					"%s_${1..%s}.main_order_${0..99}", DB_NAME, dbCount));

			TableRuleConfiguration rule_OrderDetail = new TableRuleConfiguration();
			rule_OrderDetail.setLogicTable("order_detail");
			rule_OrderDetail.setActualDataNodes(String.format(
					"%s_${1..%s}.order_detail_${0..99}", DB_NAME, dbCount));

			TableRuleConfiguration rule_Payment = new TableRuleConfiguration();
			rule_Payment.setLogicTable("payment");
			rule_Payment.setActualDataNodes(String.format(
					"%s_${1..%s}.payment_${0..99}", DB_NAME, dbCount));

			TableRuleConfiguration rule_OrderSyncCmd = new TableRuleConfiguration();
			rule_OrderSyncCmd.setLogicTable("order_sync_cmd");
			rule_OrderSyncCmd.setActualDataNodes(String.format(
					"%s_${1..%s}.order_sync_cmd_${0..99}", DB_NAME, dbCount));

			TableRuleConfiguration rule_PaymentDetail = new TableRuleConfiguration();
			rule_PaymentDetail.setLogicTable("payment_detail");
			rule_PaymentDetail.setActualDataNodes(String.format(
					"%s_${1..%s}.payment_detail_${0..99}", DB_NAME, dbCount));
			
			TableRuleConfiguration rule_shenqiProxyChain = new TableRuleConfiguration();
			rule_shenqiProxyChain.setLogicTable("shenqi_proxy_chain");
			rule_shenqiProxyChain.setActualDataNodes(String.format(
					"%s_${1..%s}.shenqi_proxy_chain_${0..99}", DB_NAME, dbCount));
			
			TableRuleConfiguration rule_shenqiSellerProfit = new TableRuleConfiguration();
			rule_shenqiSellerProfit.setLogicTable("shenqi_seller_profit");
			rule_shenqiSellerProfit.setActualDataNodes(String.format(
					"%s_${1..%s}.shenqi_seller_profit_${0..99}", DB_NAME, dbCount));
			
			TableRuleConfiguration rule_orderExt = new TableRuleConfiguration();
			rule_orderExt.setLogicTable("order_ext");
			rule_orderExt.setActualDataNodes(String.format(
					"%s_${1..%s}.order_ext_${0..99}", DB_NAME, dbCount));
			
			TableRuleConfiguration rule_orderDetailExt = new TableRuleConfiguration();
			rule_orderDetailExt.setLogicTable("order_detail_ext");
			rule_orderDetailExt.setActualDataNodes(String.format(
					"%s_${1..%s}.order_detail_ext_${0..99}", DB_NAME, dbCount));
			
			TableRuleConfiguration rule_orderDetailSubCode = new TableRuleConfiguration();
			rule_orderDetailSubCode.setLogicTable("order_detail_sub_code");
			rule_orderDetailSubCode.setActualDataNodes(String.format(
					"%s_${1..%s}.order_detail_sub_code_${0..99}", DB_NAME, dbCount));

			TableRuleConfiguration rule_OrderBiz = new TableRuleConfiguration();
			rule_OrderBiz.setLogicTable("order_biz");
			rule_OrderBiz.setActualDataNodes(String.format(
					"%s_${1..%s}.order_biz", DB_NAME, dbCount));

			TableRuleConfiguration rule_OrderBizLog = new TableRuleConfiguration();
			rule_OrderBizLog.setLogicTable("order_biz_log");
			rule_OrderBizLog.setActualDataNodes(String.format(
					"%s_${1..%s}.order_biz_log", DB_NAME, dbCount));

			TableRuleConfiguration rule_OrderStatusLog = new TableRuleConfiguration();
			rule_OrderStatusLog.setLogicTable("order_status_log");
			rule_OrderStatusLog.setActualDataNodes(String.format(
					"%s_${1..%s}.order_status_log", DB_NAME, dbCount));

			TableRuleConfiguration rule_OrderDetailPromotion = new TableRuleConfiguration();
			rule_OrderDetailPromotion.setLogicTable("order_detail_promotion");
			rule_OrderDetailPromotion.setActualDataNodes(String.format(
					"%s_${1..%s}.order_detail_promotion", DB_NAME, dbCount));

			TableRuleConfiguration rule_OrderDetailSub = new TableRuleConfiguration();
			rule_OrderDetailSub.setLogicTable("order_detail_sub");
			rule_OrderDetailSub.setActualDataNodes(String.format(
					"%s_${1..%s}.order_detail_sub", DB_NAME, dbCount));

			TableRuleConfiguration rule_refund_bill = new TableRuleConfiguration();
			rule_refund_bill.setLogicTable("refund_bill");
			rule_refund_bill.setActualDataNodes(String.format(
					"%s_${1..%s}.refund_bill", DB_NAME, dbCount));

			TableRuleConfiguration rule_refund_fund = new TableRuleConfiguration();
			rule_refund_fund.setLogicTable("refund_fund");
			rule_refund_fund.setActualDataNodes(String.format(
					"%s_${1..%s}.refund_fund", DB_NAME, dbCount));

			TableRuleConfiguration seller_refund_fund_detail = new TableRuleConfiguration();
			seller_refund_fund_detail
					.setLogicTable("seller_refund_fund_detail");
			seller_refund_fund_detail.setActualDataNodes(String.format(
					"%s_${1..%s}.seller_refund_fund_detail", DB_NAME, dbCount));

			TableRuleConfiguration user_refund_fund_detail = new TableRuleConfiguration();
			user_refund_fund_detail.setLogicTable("user_refund_fund_detail");
			user_refund_fund_detail.setActualDataNodes(String.format(
					"%s_${1..%s}.user_refund_fund_detail", DB_NAME, dbCount));

			TableRuleConfiguration card_refund_fund_detail = new TableRuleConfiguration();
			card_refund_fund_detail.setLogicTable("card_refund_fund_detail");
			card_refund_fund_detail.setActualDataNodes(String.format(
					"%s_${1..%s}.card_refund_fund_detail", DB_NAME, dbCount));

			TableRuleConfiguration prepaid_card = new TableRuleConfiguration();
			prepaid_card.setLogicTable("prepaid_card");
			prepaid_card.setActualDataNodes(String.format(
					"%s_${1..%s}.prepaid_card", DB_NAME, dbCount));

			TableRuleConfiguration auto_receive_opt = new TableRuleConfiguration();
			auto_receive_opt.setLogicTable("auto_receive_opt");
			auto_receive_opt.setActualDataNodes(String.format(
					"%s_${1..%s}.auto_receive_opt", DB_NAME, dbCount));

			TableRuleConfiguration auto_receive_opt_detail = new TableRuleConfiguration();
			auto_receive_opt_detail.setLogicTable("auto_receive_opt_detail");
			auto_receive_opt_detail.setActualDataNodes(String.format(
					"%s_${1..%s}.auto_receive_opt_detail", DB_NAME, dbCount));

			TableRuleConfiguration extend_receive = new TableRuleConfiguration();
			extend_receive.setLogicTable("extend_receive");
			extend_receive.setActualDataNodes(String.format(
					"%s_${1..%s}.extend_receive", DB_NAME, dbCount));
			
			TableRuleConfiguration delivery_biz = new TableRuleConfiguration();
			delivery_biz.setLogicTable("delivery_biz");
			delivery_biz.setActualDataNodes(String.format(
					"%s_${1..%s}.delivery_biz", DB_NAME, dbCount));
			
			TableRuleConfiguration out_promotion = new TableRuleConfiguration();
			out_promotion.setLogicTable("out_promotion");
			out_promotion.setActualDataNodes(String.format(
					"%s_${1..%s}.out_promotion", DB_NAME, dbCount));
			
			TableRuleConfiguration order_traveler = new TableRuleConfiguration();
			order_traveler.setLogicTable("order_traveler");
			order_traveler.setActualDataNodes(String.format(
					"%s_${1..%s}.order_traveler", DB_NAME, dbCount));
			
			TableRuleConfiguration order_coupon_detail = new TableRuleConfiguration();
			order_coupon_detail.setLogicTable("order_coupon_detail");
			order_coupon_detail.setActualDataNodes(String.format(
					"%s_${1..%s}.order_coupon_detail", DB_NAME, dbCount));
			
			TableRuleConfiguration pick_in_store = new TableRuleConfiguration();
			pick_in_store.setLogicTable("pick_in_store");
			pick_in_store.setActualDataNodes(String.format(
					"%s_${1..%s}.pick_in_store", DB_NAME, dbCount));

			ShardingRuleConfiguration ruleConfig = new ShardingRuleConfiguration();
			
			// 添加规则配置
			// 分表分库
			ruleConfig.getTableRuleConfigs().add(rule_Order);
			ruleConfig.getTableRuleConfigs().add(rule_MainOrder);
			ruleConfig.getTableRuleConfigs().add(rule_OrderDetail);
			ruleConfig.getTableRuleConfigs().add(rule_Payment);
			ruleConfig.getTableRuleConfigs().add(rule_PaymentDetail);
			ruleConfig.getTableRuleConfigs().add(rule_shenqiProxyChain);
			ruleConfig.getTableRuleConfigs().add(rule_shenqiSellerProfit);
			ruleConfig.getTableRuleConfigs().add(rule_orderExt);
			ruleConfig.getTableRuleConfigs().add(rule_orderDetailExt);
			ruleConfig.getTableRuleConfigs().add(rule_orderDetailSubCode);
			
			//分表不分库
			ruleConfig.getTableRuleConfigs().add(rule_OrderSyncCmd);
			ruleConfig.getTableRuleConfigs().add(rule_OrderBiz);
			ruleConfig.getTableRuleConfigs().add(rule_OrderDetailPromotion);
			ruleConfig.getTableRuleConfigs().add(rule_OrderStatusLog);
			ruleConfig.getTableRuleConfigs().add(rule_OrderBizLog);
			ruleConfig.getTableRuleConfigs().add(rule_OrderDetailSub);
			ruleConfig.getTableRuleConfigs().add(rule_refund_bill);
			ruleConfig.getTableRuleConfigs().add(rule_refund_fund);
			ruleConfig.getTableRuleConfigs().add(seller_refund_fund_detail);
			ruleConfig.getTableRuleConfigs().add(user_refund_fund_detail);
			ruleConfig.getTableRuleConfigs().add(card_refund_fund_detail);
			ruleConfig.getTableRuleConfigs().add(prepaid_card);
			ruleConfig.getTableRuleConfigs().add(auto_receive_opt);
			ruleConfig.getTableRuleConfigs().add(auto_receive_opt_detail);
			ruleConfig.getTableRuleConfigs().add(extend_receive);
			ruleConfig.getTableRuleConfigs().add(delivery_biz);
			ruleConfig.getTableRuleConfigs().add(out_promotion);
			ruleConfig.getTableRuleConfigs().add(order_traveler);
			ruleConfig.getTableRuleConfigs().add(order_coupon_detail);
			ruleConfig.getTableRuleConfigs().add(pick_in_store);

			//需要分表中必须有一个字段是属于shardingColumns, 否则将在100张表中操作

			//多字段分库策略
			ruleConfig
					.setDefaultDatabaseShardingStrategyConfig(new ComplexShardingStrategyConfiguration(
							"order_id,main_order_id,payment_id,user_id",
							ComplexKeysDatabaseAlgorithm.class.getName()));
			//多字段分表策略
			ruleConfig
					.setDefaultTableShardingStrategyConfig(new ComplexShardingStrategyConfiguration(
							"order_id,main_order_id,payment_id,user_id",
							ComplexKeysTableAlgorithm.class.getName()));
			
			Properties properties = new Properties();
			
			// 打开sql日志
//			properties.setProperty("sql.show", Boolean.TRUE.toString());
			
			return ShardingDataSourceFactory
					.createDataSource(dataSourceMap, ruleConfig,
							new HashMap<String, Object>(), properties);

		} catch (SQLException e) {
			throw new RuntimeException("Failed to create sharding data source",
					e);
		}
	}

	public SqlSessionFactory shardingSessionFactory() throws Exception {

		SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();

		// 获取properties中的对应配置信息
		String mapperLocation = "classpath*:com/ymttest/database/sharding/sqlmap/sharding/**/*.xml";
		//	String configLocation = "classpath*:config/mybatis-settings.xml";

		/*		String configLocation = System.getProperty("user.dir") + File.separator
						+ "config" + File.separator + "mybatis-settings.xml";*/

		Properties properties = new Properties();
		
		ShardingDataSource sds = (ShardingDataSource) shardingDataSource();
		
		System.out.println(sds.getConnection().isClosed());

		sessionFactory.setDataSource(sds);
		sessionFactory.setConfigurationProperties(properties);

		// 设置MapperLocations configLocation路径
		ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();

		/*	System.out.println("mapperLocation size:"
					+ resourcePatternResolver.getResources(mapperLocation).length);
		*/
		sessionFactory.setMapperLocations(resourcePatternResolver
				.getResources(mapperLocation));

		/*		sessionFactory.setConfigLocation(resourcePatternResolver
						.getResource(configLocation));*/

		return sessionFactory.getObject();
	}
	
	public SqlSessionTemplate shardingSessionTemplate() throws Exception {
		return new SqlSessionTemplate(shardingSessionFactory());
	}

	public PlatformTransactionManager shardingTransactionManager() {
		return new DataSourceTransactionManager(shardingDataSource());
	}

	public TransactionTemplate shardingTransactionTemplate() {
		return new TransactionTemplate(shardingTransactionManager());
	}

	public static DbShardingSourceConfig getInstance() {

		if (shardingSourceConfig == null) {

			shardingSourceConfig = new DbShardingSourceConfig();
		}

		return shardingSourceConfig;
	}
}
